//-----------------------------------------------------------------------------
//
// cache.c
//
// Group:
//    Doug Kline,          dakline@ncsu.edu
//    Mauricio Del Carpio, mgdelcar@ncsu.edu
//    Josh Jodrey,         jdjodrey@ncsu.edu
//
//
// This class handles cacheing web pages.
//
//-----------------------------------------------------------------------------

#include "cache.h"
#include "serverHandler.h"

// Debug facility, see proxy.c
extern int info;   /* Basic infomation */
extern int debug;  /* Full debug output */


extern struct Config g_config;

static struct cacheItem *head_p = NULL;

DECLARE_MUTEX(g_listLock);

static int listSize = 0;

static void removeItem(struct cacheItem *item_p);
static struct cacheItem *itemFind(char *host_p, char *path_p);

static void lock(struct cacheItem *item_p);
static int  trylock(struct cacheItem *item_p);

static void lockList(void);
static void unlockList(void);


//-----------------------------------------------------------------------------
// cacheGet()
//
// Will check the cache to see if the request is cached.  If so, will send a
// conditional GET to see if the cached object is still good.
//
// The item returned by this function is locked and must be unlocked by the caller.
//
//-----------------------------------------------------------------------------
struct cacheItem *cacheGet(char *command_p, char *host_p, char *path_p, 
                           struct sockaddr_in clientAddr)

{
   struct cacheItem *item_p = NULL;
   int timedOut = 0;
   int err;
   int timeRemaining;
   int fromCache;

   if ( (command_p == NULL) ||
        (host_p    == NULL) ||
        (path_p    == NULL) )
   {
      printk(KERN_ERR "ERROR: cacheGet entered with NULL pointer\n");
      return NULL;
   }
   
   //--------------------------------------------------------------------------
   // First look to see if it is in the list
   item_p = itemFind(host_p, path_p);
   
   if (item_p)
   {
      // If found, then the item is locked and has been moved to the head of list
      //
      // Is there a Last-Modified timestamp?
      
      if ((item_p->valid) && (item_p->lastMod[0] == '\0'))
      {
         // No timestamp so return cached if time remaining
         struct timeval curTime;
         
         do_gettimeofday(&curTime);
         
         timeRemaining = g_config.cacheTime - 
                             (curTime.tv_sec - item_p->timestamp.tv_sec);
         
         if (timeRemaining > 0)
         {
            if(info)
            {
               printk(KERN_INFO "......\n");
               printk(KERN_INFO "%s/%s\n", host_p, path_p);
               printk(KERN_INFO "   found in cache; time remaining is %d sec\n", timeRemaining);
            
            }
      
            return item_p; // returned locked
         }
         else
         {
            // Clean up this item and create a new one below
            timedOut = 1;
            
            removeItem(item_p);
            deleteCacheItem(item_p); // will unlock(item_p)
            
         }
      }
      
      if ( !timedOut )
      {
         // item_p should be locked on entry to this block
         if(debug) printk(KERN_DEBUG "\n");
         
         // Now we need to do a conditional GET to see if it is still current
         err = serverConditionalGET(item_p, &fromCache);
         
         if (err)
         {
            unlock(item_p);
            return NULL;
         }
         
         if (item_p->valid == 0)
         {
            if(debug) printk(KERN_DEBUG "serverConditionalGet returned an invalid item\n");
            unlock(item_p);
            return NULL;
         }
         
         if(info)
         {
            printk(KERN_INFO "......\n");
            printk(KERN_INFO "%s/%s\n", host_p, path_p);
            
            if(fromCache)
            {
               printk(KERN_INFO "   found in cache; verified to be current\n");
            }
            else
            {
               printk(KERN_INFO "   found old in cache; updated from real server\n");
            }
            
            printk(KERN_INFO "   Last-Modified: %s\n", item_p->lastMod);
         }
         
      
         return item_p; // return locked
      }
   }
   
   // if timedOut, then start like new
   
   //--------------------------------------------------------------------------
   // Grap a cache item to store info into
   item_p = newCacheItem();
   
   if (item_p == NULL)
   {
      printk(KERN_ERR "ERROR: failed to allocate a cache item\n");
      return NULL;
   }
   
   strcpy(item_p->hostname, host_p);
   strcpy(item_p->path,     path_p);
   item_p->clientAddr = clientAddr;

   // Lock the item and place the it in the list
   lock(item_p);
   insertItem(item_p);

   // Now get the content from the server
   err = serverGET(command_p, item_p);
   
   if (err)
   {
      unlock(item_p);
      return NULL;
   }
       
         
   if (item_p->valid)
   {
      if(info)
      {
         printk(KERN_INFO "......\n");
         printk(KERN_INFO "%s/%s\n", host_p, path_p);
         printk(KERN_INFO "   read requested object from real server\n");
         printk(KERN_INFO "   Last-Modified: %s\n", item_p->lastMod);
         printk(KERN_INFO "   Size: %d\n", item_p->size);
      }
      return item_p; // return locked
   }
   else
   {
      if(info)
      {
         printk(KERN_INFO "......\n");
         printk(KERN_INFO "%s/%s\n", host_p, path_p);
         printk(KERN_INFO "   could not get requested object\n");
      }
   
      removeItem(item_p);
      deleteCacheItem(item_p); // must be locked
      return NULL;
   }
   
   
} // end cacheGet()


//-----------------------------------------------------------------------------
// newCacheItem()
//
// Allocates a new cache item, but does not yet place it in the cache item list.
//
//-----------------------------------------------------------------------------
struct cacheItem *newCacheItem(void)
{
   struct cacheItem *item_p;
   
   item_p = (struct cacheItem *)(kmalloc(sizeof(struct cacheItem), GFP_KERNEL));
   
   if (item_p == NULL)
   {
      printk(KERN_ERR "ERROR: did not kmalloc a cacheItem\n");
      return NULL;
   }
   
   memset(item_p, 0, sizeof(struct cacheItem));
   
   init_MUTEX(&(item_p->itemLock));
   
   // Set the time here
   
   do_gettimeofday(&(item_p->timestamp));
   
   // Set the port
   item_p->server_port = 80; // default http
   
   return item_p;

} // newCacheItem()



//-----------------------------------------------------------------------------
// deleteCacheItem()
//
// Frees a cache item
//
// The item needs to be locked when this is called
//
//-----------------------------------------------------------------------------
void deleteCacheItem(struct cacheItem *item_p)
{
   if(item_p == NULL) 
      return;
      
   item_p->valid = 0;
      
   // Delete the buffer first
   if (item_p->buf_p)
   {
      kfree(item_p->buf_p);
      item_p->buf_p = NULL;
   }

   unlock(item_p);
   
   kfree(item_p);
   

} // deleteCacheItem()




//-----------------------------------------------------------------------------
// insertItem()
//
// Places a new cache item at the top of the list, older items below
//
//-----------------------------------------------------------------------------
void insertItem(struct cacheItem *item_p)
{
   lockList();
   
   if (head_p == NULL)
   {
      // First item
      head_p = item_p;
      item_p->next_p = item_p;
      item_p->prev_p = item_p;
   }
   else if (head_p == head_p->next_p)
   {
      // Only one item
      item_p->prev_p = head_p->prev_p;
      head_p->prev_p = item_p;
      item_p->next_p = head_p;
      head_p->next_p = item_p;
      head_p = item_p;
   }
   else
   {
      // At least two items
      item_p->prev_p = head_p->prev_p;
      head_p->prev_p->next_p = item_p;
      head_p->prev_p = item_p;
      item_p->next_p = head_p;
      head_p = item_p;
   }
   
   // Increase the total size
   listSize += sizeof(struct cacheItem);
   listSize += item_p->size;
   
   unlockList();

} // end insertItem()

//-----------------------------------------------------------------------------
// removeItem()
//
// Removes an item from the list
//
//-----------------------------------------------------------------------------
void removeItem(struct cacheItem *item_p)
{
   if (item_p == NULL)
   {
      return;
   }   

   if (head_p == NULL)  
   {
      return;
   }   

   lockList();


   if (item_p == head_p)
   {
      // How many items?
      if (item_p->next_p == item_p)
      {
         // Only one item
         head_p = NULL;
      }
      else
      {
         // At least 2 items, removing head item
         head_p = head_p->next_p;
         head_p->prev_p = item_p->prev_p;
         item_p->prev_p->next_p = head_p;
      }
   }
   else
   {   
      // Not head, so at least 2 items, not removing head
      item_p->next_p->prev_p = item_p->prev_p;
      item_p->prev_p->next_p = item_p->next_p;
   }

   // Decrease the total size
   listSize -= sizeof(struct cacheItem);
   listSize -= item_p->size;

   unlockList();

} // end removeItem()


//-----------------------------------------------------------------------------
// itemFind()
//
// If found, the item is moved to the head of the list for the LRU
// replacement policy and then returned locked.
//
//-----------------------------------------------------------------------------
struct cacheItem *itemFind(char *host_p, char *path_p)
{
   struct cacheItem *temp_p = NULL;
   struct cacheItem *item_p  = NULL;
   
   lockList();
   
   if (head_p == NULL)
   {
      unlockList();
      return NULL;
   }
   
   temp_p = head_p;
   
   do 
   {
      if ( !(strcmp(temp_p->hostname, host_p)) &&  // strcmp is 0 if compares
           !(strcmp(temp_p->path, path_p)) )
      {
         // found it
         item_p = temp_p;
         break;
      }

      temp_p = temp_p->next_p;
      
   } while(temp_p != head_p);
   
   // Lock the item and move it to the head of the list
   if (item_p)
   {
      lock(item_p);
      
      if (item_p == head_p)
      {
         // Done
      }
      else
      {
         struct cacheItem *prev_p = item_p->prev_p;
         struct cacheItem *next_p = item_p->next_p;
         
         // Remove from list
         prev_p->next_p = item_p->next_p;
         next_p->prev_p = item_p->prev_p;
         
         // Now insert at head
         if (head_p == head_p->next_p)
         {
            // Only one item
            item_p->prev_p = head_p->prev_p;
            head_p->prev_p = item_p;
            item_p->next_p = head_p;
            head_p->next_p = item_p;
            head_p = item_p;
         }
         else
         {
            // At least two items
            item_p->prev_p = head_p->prev_p;
            head_p->prev_p->next_p = item_p;
            head_p->prev_p = item_p;
            item_p->next_p = head_p;
            head_p = item_p;
         }
         
      }
   }
   
   unlockList();

   return item_p;
 
} // end itemFind()




//-----------------------------------------------------------------------------
// lock()
// trylock() - returns EBUSY if can not lock
// unlock()
//
//-----------------------------------------------------------------------------
void lock(struct cacheItem *item_p)
{
   down( &(item_p->itemLock) );
}

int trylock(struct cacheItem *item_p)
{
   return down_trylock( &(item_p->itemLock) );
}

void unlock(struct cacheItem *item_p)
{
   up( &(item_p->itemLock) );
}

//-----------------------------------------------------------------------------
// lockList()
// trylockList() - returns EBUSY if can not lock
// unlockList()
//
//
//-----------------------------------------------------------------------------
void lockList(void)
{
   down( &g_listLock );
}

#if 0
int trylockList(void)
{
   return down_trylock( &g_listLock );
}
#endif

void unlockList(void)
{
   up( &g_listLock );
}



//-----------------------------------------------------------------------------
// updateListSize()
//
// This function updates the total amount of memory used by the list and
// deletes old cache items if space is needed.
//
//-----------------------------------------------------------------------------
void updateListSize(int size)
{
   // Lock down the list & listSize variable
   lockList();
   
   listSize += size;
   
   while ( (g_config.cacheSize * 1024) < listSize )
   {
       struct cacheItem *item_p;
       
       item_p = head_p->prev_p;
       
       if (item_p == head_p)
       {
          printk(KERN_ERR "ERROR: cache has only one item and is out of memory - increase cache size\n");
          unlockList();
          return;
       }
       
       while (1)
       {
          if (trylock(item_p) != EBUSY)
          {
              // Item was available
              break;
          }
          
          // Go on to the next item
          item_p = head_p->prev_p;
          
          if (item_p == head_p)
          {
             // Gone full circle, will fix cache size next time
             unlockList();
             return;
          }
       }
       
       // Now remove the item
       item_p->next_p->prev_p = item_p->prev_p;
       item_p->prev_p->next_p = item_p->next_p;

       // Decrease the total size
       listSize -= sizeof(struct cacheItem);
       listSize -= item_p->size;
       if(info) printk(KERN_INFO "deleting from cache: %s/%s\n", item_p->hostname, item_p->path);

       deleteCacheItem(item_p); // Item must be locked
       
   }

   unlockList();

} // end updateListSize()



//-----------------------------------------------------------------------------
// appendBuf()
//
// This function appends a given buffer contents onto the buffer in the cache item.
// The given buffer is not altered.
//
// The item must already be locked when this is called.
//
//-----------------------------------------------------------------------------
void appendBuf(struct cacheItem *item_p, void *buf_p, int size)
{
   int totalSize;
   void *temp_p;
   
   // Quick and dirty, just allocate a bigger buffer each time
   
   // Update the total list size and make room if needed
   updateListSize(size);
   
   totalSize = item_p->size + size;
   
   temp_p = kmalloc(totalSize, GFP_KERNEL);
   
   if (temp_p == NULL)
   {
      printk(KERN_ERR "ERROR: failed kmalloc call\n");
      return;
   }
   
   // If there is existing info in the cache item, then copy into the new buffer
   if (item_p->buf_p != NULL)
   {
       memcpy(temp_p, item_p->buf_p, item_p->size);
   }
   
   // Now copy in the new buffer
   memcpy( (temp_p + item_p->size), buf_p, size);
   
   // And fix up the size
   item_p->size += size;
   
   // Free the old memory and replace in cache item
   if(item_p->buf_p)
   {
      kfree(item_p->buf_p);
   }

   item_p->buf_p = temp_p;

} // end appendBuf()


